{% set class_name = cls.name %}
{% set js_class_name = class_name %}
{% if class_name == "Object" %}
	{% set js_class_name = "GodotObject" %}
{% endif %}
{% set snake_name = camel_to_snake(class_name) %}
{% set js_snake_name = camel_to_snake(js_class_name) %}
{% set objects = classes | map(attribute="name") | list %}
{% set pod_types = ['Nil', 'bool', 'int', 'float', 'String', 'StringName', 'NodePath'] %}
{% set instance_methods = cls.methods | selectattr('is_vararg', 'equalto', false) | selectattr('is_virtual', 'equalto', false) | selectattr('is_static', 'equalto', false) | list %}
{% set static_methods = cls.methods | selectattr('is_vararg', 'equalto', false) | selectattr('is_virtual', 'equalto', false) | selectattr('is_static', 'equalto', true) | list %}
{% set vararg_methods = cls.methods | selectattr('is_vararg', 'equalto', true) | list %}
#include "register/classes/register_classes.hpp"
#include "register/object_proxy.hpp"
#include "utils/env.hpp"
#include "utils/func_utils.hpp"
#include "utils/variant_helper.hpp"
#include "utils/quickjs_helper.hpp"
#include "support/module_loader.hpp"
{% if has_vararg_methods %}
#include "register/classes/class_{{ snake_name }}_vararg.hpp"
{% endif %}
#include <quickjs.h>
{% for den in dependency %}
#include <godot_cpp/classes/{{ den }}.hpp>
{% endfor %}
#include <godot_cpp/variant/builtin_types.hpp>
#include <godot_cpp/classes/{{ snake_name }}.hpp>
{% macro get_const(method) %}{{ '_const' if method.is_const else '' }}{% endmacro %}
{%- macro clean_type(e) -%}
	{%- if e.startswith("enum::") -%}
{{-e[6:] | replace(".", "::")-}}
	{%- elif e.startswith("bitfield::") -%}
{{-e[10:] | replace(".", "::")}}
	{%- elif "int" in e and "typedarray::" in e-%}
PackedInt32Array
	{%- elif e.startswith("typedarray::") -%}
Packed{{-e[12] | upper -}}{{-e[13:]-}}Array
	{%- else -%}
{{-e-}}
	{%- endif -%}
{%- endmacro -%}
{% macro get_array_typed(e) %}
	{%- if e.startswith("typedarray::") -%}
TypedArray<{{-e[12:]-}}>
	{%- else -%}
{{e}}
	{% endif %}
{% endmacro %}
{% macro clean_setter(e) %}
{%- if e[0] == "_" -%}
{{-e[1:]-}}
{%- else -%}
{{-e-}}
{%- endif -%}
{% endmacro %}
using namespace godot;

static void {{ js_snake_name }}_class_finalizer(JSRuntime *rt, JSValue val) {
	JSClassID class_id = classes["{{ js_class_name }}"];
	VariantAdapter *opaque_ptr = static_cast<VariantAdapter *>(JS_GetOpaque(val, class_id));
	if (opaque_ptr) {
		{% if cls['is_refcounted'] %}
        if (opaque_ptr->can_unref){
            static_cast<RefCounted *>(opaque_ptr->get().operator Object *())->unreference();
        }
		{% endif %}
		memdelete(opaque_ptr);
	}
}

static JSClassDef {{ js_snake_name }}_class_def = {
    "{{ js_class_name }}",
    {{ js_snake_name }}_class_finalizer
};

static JSValue {{ js_snake_name }}_class_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) {
    JSClassID class_id = classes["{{ js_class_name }}"];
    JSValue proto = JS_GetPropertyStr(ctx, new_target, "prototype");
    JSValue obj = JS_NewObjectProtoClass(ctx, proto, class_id);
	if (is_exception(ctx, obj)) {
		return obj;
	}

	VariantAdapter *adapter = nullptr;
	Object *instance = nullptr;
    if (argc == 1 && VariantAdapter::can_cast(argv[0], Variant::Type::OBJECT)) {
    	instance = static_cast<VariantAdapter *>(JS_GetOpaque(*argv, class_id))->get();
		adapter = memnew(VariantAdapter(instance));
    } else {
        instance = memnew({{ class_name }});
	 	adapter = memnew(VariantAdapter(instance));
    }

    if (!instance) {
        JS_FreeValue(ctx, obj);
        return JS_EXCEPTION;
    }
    JS_SetOpaque(obj, adapter);
    return obj;
}

{% for method in instance_methods %}
	{% if class_name == 'FileAccess' and method['name'] == 'get_buffer' %}
static JSValue {{js_snake_name}}_class_{{method['name']}}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
	CHECK_INSTANCE_VALID_V(this_val);
	return call_builtin_const_method_ret(static_cast<PackedByteArray(FileAccess::*)(int64_t) const>(&FileAccess::get_buffer), ctx, this_val, argc, argv);
};
	{% elif class_name == 'FileAccess' and method['name'] == 'store_buffer' %}
static JSValue {{js_snake_name}}_class_{{method['name']}}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
	CHECK_INSTANCE_VALID_V(this_val);
	return call_builtin_method_ret(static_cast<bool(FileAccess::*)(const PackedByteArray &)>(&FileAccess::store_buffer), ctx, this_val, argc, argv);
};
	{% else %}
		{% if method['name'] in cls["properties"] | map(attribute="getter") %}
		{% set prop1 = cls["properties"] | selectattr("getter", "equalto",  method["name"]) | first %}
		{% set getter_method = instance_methods | selectattr("name", "equalto",  prop1["getter"]) | first %}
		{% set setter_method = instance_methods | selectattr("name", "equalto",  prop1['setter']) | first %}
		{% set proxy_type = clean_type(getter_method['return_value']['type']) %}
		{% set typed = get_array_typed(getter_method['return_value']['type']) %}
		{% set return_type = getter_method['return_value']['type'] %}
static JSValue {{js_snake_name}}_class_{{method['name']}}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
	{% if prop1['type'] in (builtin_classes | map(attribute='name') | list) and prop1['type'] not in pod_types and 'typedarray::' not in return_type %}
	CHECK_INSTANCE_VALID_V(this_val);
	ObjectProxy<{{clean_type(return_type)}}> *proxy = memnew(ObjectProxy<{{clean_type(return_type)}}>);
	proxy->wrapped = VariantAdapter(this_val).get();
	{% if prop1["getter"] %}
	proxy->getter = [this_val]() -> {{proxy_type}} {
		{{class_name}} *obj = static_cast<{{class_name}} *>(VariantAdapter(this_val).get().operator Object*());
		return obj->{{prop1["getter"]}}();
	};
	{% endif %}
	{% if prop1["setter"] %}
	proxy->setter = [this_val](const {{proxy_type}} &value) -> void {
		{{class_name}} *js_proxy = static_cast<{{class_name}} *>(VariantAdapter(this_val).get().operator Object *());
		{% set setter =  instance_methods | selectattr("name" , "equalto", prop1["setter"]) | first %}
		{% if proxy_type.startswith("Packed") %}
		js_proxy->{{-clean_setter(prop1["setter"])-}}({{-typed-}}(Array(value)));
		{% else %}
		js_proxy->{{-clean_setter(prop1["setter"])-}}(value);
		{% endif %}
	};
	{% endif %}
	JSValue obj = JS_NewObjectClass(ctx, classes["{{proxy_type}}Proxy"]);
	if (is_exception(ctx, obj)) {
		return JS_EXCEPTION;
	}
	JS_SetOpaque(obj, proxy);
	JSValue global = JS_GetGlobalObject(ctx);
	JSValue obj_constructor = JS_GetPropertyStr(ctx, global, "{{proxy_type}}Proxy");
	JSValue js_proxy = JS_CallConstructor(ctx, obj_constructor, 1, &obj);
    return js_proxy;
	{% else %}
	CHECK_INSTANCE_VALID_V(this_val);
		{% if method['return_value'] %}
			{% if class_name == 'Node' and method['name'] == 'get_node' %}
	return call_builtin_const_method_ret(&Node::get_node_internal, ctx, this_val, argc, argv);
			{% else %}
	return call_builtin{{get_const(method)}}_method_ret(&{{class_name}}::{{method['name']}}, ctx, this_val, argc, argv);
			{% endif %}
		{% else %}
    return call_builtin{{get_const(method)}}_method_no_ret(&{{class_name}}::{{method['name']}}, ctx, this_val, argc, argv);
		{% endif %}
	{% endif %}
}
		{% else %}
static JSValue {{js_snake_name}}_class_{{method['name']}}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
	CHECK_INSTANCE_VALID_V(this_val);
		{% if method['return_value'] %}
			{% if class_name == 'Node' and method['name'] == 'get_node' %}
	return call_builtin_const_method_ret(&Node::get_node_internal, ctx, this_val, argc, argv);
			{% else %}
	return call_builtin{{get_const(method)}}_method_ret(&{{class_name}}::{{method['name']}}, ctx, this_val, argc, argv);
			{% endif %}
		{% else %}
    return call_builtin{{get_const(method)}}_method_no_ret(&{{class_name}}::{{method['name']}}, ctx, this_val, argc, argv);
		{% endif %}
};
		{% endif %}
	{% endif %}
{% endfor %}

{% for prop in cls['properties'] %}
	{% if 'index' in prop %}
		{% if prop['getter']  and not prop['getter'].startswith('_') %}
static JSValue {{js_snake_name}}_class_get_{{prop['name']}}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) {
	CHECK_INSTANCE_VALID_V(this_val);
	JSValue index = JS_NewInt64(ctx, magic);
	JSValue ret = call_builtin_const_method_ret(&{{class_name}}::{{prop['getter']}}, ctx, this_val, argc, &index);
	JS_FreeValue(ctx, index);
	return ret;
}
		{% endif %}
		{% if prop['setter'] and not prop['setter'].startswith('_') %}
static JSValue {{js_snake_name}}_class_set_{{prop['name']}}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) {
	CHECK_INSTANCE_VALID_V(this_val);
	JSValue index = JS_NewInt64(ctx, magic);
	std::vector<JSValueConst> vec_arg;
	vec_arg.push_back(index);
	vec_arg.reserve(argc + 1);
	vec_arg.insert(vec_arg.end(), argv, argv + argc);
	call_builtin_method_no_ret(&{{class_name}}::{{prop['setter']}}, ctx, this_val, argc, vec_arg.data());
	return JS_UNDEFINED;
}
		{% endif %}
	{% endif %}
{% endfor %}

{% for method in static_methods %}
static JSValue {{js_snake_name}}_class_{{method['name']}}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    {% if method['return_value'] %}
	return call_builtin_static{{get_const(method)}}_method_ret(&{{class_name}}::{{method['name']}}, ctx, this_val, argc, argv);
    {% else %}
    return call_builtin_static{{get_const(method)}}_method_no_ret(&{{class_name}}::{{method['name']}}, ctx, this_val, argc, argv);
    {% endif %}
};
{% endfor %}
{% for method in vararg_methods %}
static JSValue {{js_snake_name}}_class_{{method['name']}}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
	CHECK_INSTANCE_VALID_V(this_val);
    {% if method['return_value'] %}
	return call_builtin_free_owner_vararg_method_ret(&js_{{method['name']}}, ctx, this_val, argc, argv);
    {% else %}
    return call_builtin_free_owner_vararg_method_no_ret(&js_{{method['name']}}, ctx, this_val, argc, argv);
    {% endif %}
}
{% endfor %}

{% if instance_methods or vararg_methods %}
static const JSCFunctionListEntry {{js_snake_name}}_class_proto_funcs[] = {
	{% for method in instance_methods + vararg_methods %}
	JS_CFUNC_DEF("{{ method['name'] }}", {{method['arguments'] | length}}, &{{js_snake_name}}_class_{{method['name']}}),
	{% endfor %}
    {% for gs in cls['properties'] %}
        {% if gs['index'] %}
            {% if gs['getter'] and not gs['getter'].startswith('_') %}
    JS_CFUNC_MAGIC_DEF("get_{{gs['name']}}", 0, &{{js_snake_name}}_class_get_{{gs['name']}}, {{gs['index']}}),
            {% endif %}
            {% if gs['setter'] and not gs['setter'].startswith('_') %}
    JS_CFUNC_MAGIC_DEF("set_{{gs['name']}}", 1, &{{js_snake_name}}_class_set_{{gs['name']}}, {{gs['index']}}),
            {% endif %}
        {% endif %}
    {% endfor%}
};
{% endif %}

{% if static_methods %}
static const JSCFunctionListEntry {{js_snake_name}}_class_static_funcs[] = {
	{% for method in static_methods %}
	JS_CFUNC_DEF("{{ method['name'] }}", {{method['arguments'] | length}}, &{{js_snake_name}}_class_{{method['name']}}),
	{% endfor %}
};
{% endif %}

{% for signal in cls['signals'] %}
static JSValue {{js_snake_name}}_class_get_{{signal['name']}}_signal(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
	CHECK_INSTANCE_VALID_V(this_val);
	JSValue global = JS_GetGlobalObject(ctx);
	JSValue signal_ctor = JS_GetPropertyStr(ctx, global, "Signal");
	JSValue signal_name = JS_NewString(ctx, "{{signal['name']}}");
	JSValue args[] = { this_val, signal_name };
	JS_FreeValue(ctx, global);
	return JS_CallConstructor(ctx, signal_ctor, 2, args);
}
{% endfor %}

{% macro getset(gs,method,type) %}
	{%-  if (gs in cls['methods'] | map(attribute='name')) and type == 'getter' %}
JS_NewCFunction(ctx, {{js_snake_name}}_class_{{gs}}, "{{gs}}", 0),
	{%- elif (gs in cls['methods'] | map(attribute='name')) and type == 'setter' %}
JS_NewCFunction(ctx, {{js_snake_name}}_class_{{gs}}, "{{gs}}", 1),
	{%- else %}
JS_UNDEFINED,
	{%- endif %}
{% endmacro %}
{% macro magic_getset(prop) %}
 	{% if not prop['getter'].startswith('_') %}
JS_NewCFunctionMagic(ctx, {{js_snake_name}}_class_get_{{prop['name']}}, "get_{{prop['name']}}", 0, JSCFunctionEnum::JS_CFUNC_generic_magic, {{prop['index']}}),
	{% else %}
		JS_UNDEFINED,
	{% endif %}
	{%- if not prop['setter'].startswith('_') %}
		JS_NewCFunctionMagic(ctx, {{js_snake_name}}_class_set_{{prop['name']}}, "set_{{prop['name']}}", 1, JSCFunctionEnum::JS_CFUNC_generic_magic, {{prop['index']}}),
	{%- else %}
		JS_UNDEFINED,
	{%- endif %}
{% endmacro %}

static void define_{{js_snake_name}}_property(JSContext *ctx, JSValue proto) {
	{% for prop in cls['properties'] %}
		{% if (prop['getter'] in cls['methods'] | map(attribute='name') or prop['setter'] in cls['methods'] | map(attribute='name')) and not 'index' in prop %}
    JS_DefinePropertyGetSet(
        ctx,
        proto,
        JS_NewAtom(ctx, "{{prop['name']}}"),
        {{ getset(prop['getter'], cls['methods'], 'getter') }}
        {{ getset(prop['setter'], cls['methods'], 'setter') }}
        JS_PROP_GETSET
    );
		{% elif (prop['getter'] in cls['methods'] | map(attribute='name') or prop['setter'] in cls['methods'] | map(attribute='name')) and 'index' in prop %}
	JS_DefinePropertyGetSet(
		ctx,
        proto,
        JS_NewAtom(ctx, "{{prop['name']}}"),
		{{magic_getset(prop)}}
		JS_PROP_GETSET
	);
		{% endif %}
	{% endfor %}
	{% for signal in cls['signals'] %}
	JS_DefinePropertyGetSet(
		ctx,
		proto,
		JS_NewAtom(ctx, "{{signal['name']}}"),
		JS_NewCFunction(ctx, {{js_snake_name}}_class_get_{{signal['name']}}_signal, "get_{{signal['name']}}_signal", 0),
		JS_UNDEFINED,
		JS_PROP_GETSET);
	{% endfor %}
}

static void define_{{js_snake_name}}_enum(JSContext *ctx, JSValue ctor) {
	{% for enum in cls['enums'] %}
	JSValue {{enum['name']}}_obj = JS_NewObject(ctx);
		{% for value in enum['values'] %}
	JS_SetPropertyStr(ctx, {{enum['name']}}_obj, "{{value['name']}}", JS_NewInt64(ctx, {{value['value']}}));
		{% endfor %}
	JS_SetPropertyStr(ctx, ctor, "{{enum['name']}}", {{enum['name']}}_obj);
	{% endfor %}
}

{% macro check_inherits(i) %}
{% if cls['inherits'] == 'Object' %}
{{- camel_to_snake('GodotObject') -}}
{% else %}
{{- camel_to_snake(cls['inherits']) -}}
{% endif %}
{% endmacro %}
{% macro base_class(c) %}
{%- if c == 'Object' -%}
GodotObject
{%- else -%}
{{-c-}}
{%- endif -%}
{% endmacro %}
static int js_{{js_snake_name}}_class_init(JSContext *ctx, JSModuleDef *m) {	
	JSClassID class_id = 0;
	class_id = JS_NewClassID(js_runtime(), &class_id);
	classes["{{ js_class_name }}"] = class_id;
	classes_by_id[class_id] = "{{ js_class_name }}";
	JS_NewClass(JS_GetRuntime(ctx), class_id, &{{js_snake_name}}_class_def);

	JSValue proto = JS_NewObjectClass(ctx, class_id);
	{% if cls['inherits'] %}
	JSValue base_class = JS_GetClassProto(ctx, classes["{{ base_class(cls['inherits']) }}"]);
	JS_SetPrototype(ctx, proto, base_class);
	{% endif %}
	JS_SetClassProto(ctx, class_id, proto);

	define_{{js_snake_name}}_property(ctx, proto);
		{% if instance_methods or vararg_methods %}
	JS_SetPropertyFunctionList(ctx, proto, {{js_snake_name}}_class_proto_funcs, _countof({{js_snake_name}}_class_proto_funcs));
		{% endif %}
	JSValue ctor = JS_NewCFunction2(ctx, {{js_snake_name}}_class_constructor, "{{ js_class_name }}", 0, JS_CFUNC_constructor, 0);
	{% if static_methods %}
	JS_SetPropertyFunctionList(ctx, ctor, {{js_snake_name}}_class_static_funcs, _countof({{js_snake_name}}_class_static_funcs));
	{% endif %}
	define_{{js_snake_name}}_enum(ctx, ctor);
	JS_SetConstructor(ctx, ctor, proto);
	JS_SetModuleExport(ctx, m, "{{ js_class_name }}", ctor);
	ctor_list["{{class_name}}"] = ctor;

	return 0;
}

JSModuleDef *_js_init_{{ js_snake_name }}_module(JSContext *ctx, const char *module_name) {
	// 需要提前完成import依赖
	{% if cls['inherits'] %}
		{% if cls['inherits'] not in singletons | map(attribute='type') | list %}
	const char *code = "import * as _ from '@godot/classes/{{check_inherits(cls['inherits'])}}';";
	JSValue module = JS_Eval(ctx, code, strlen(code), "<eval>", JS_EVAL_TYPE_MODULE);
	if (JS_IsException(module))
		return NULL;
		{% endif %}
	{% endif %}
	JSModuleDef *m = JS_NewCModule(ctx, module_name, js_{{js_snake_name}}_class_init);
	if (!m)
		return NULL;
	JS_AddModuleExport(ctx, m, "{{js_class_name}}");
	return m;
}

JSModuleDef *js_init_{{ js_snake_name }}_module(JSContext *ctx) {
	return _js_init_{{ js_snake_name }}_module(ctx, "@godot/classes/{{js_snake_name}}");
}

void __register_{{ js_snake_name }}() {
	js_init_{{ js_snake_name }}_module(js_context());
}

void register_{{ js_snake_name }}() {
	{# (*register_func_map())["{{snake_name}}"] = __register_{{js_snake_name}};
	(*registered_classes()).insert("{{snake_name}}"); #}
	__register_{{js_snake_name}}();
}